package com.zusmart.core.context.annotation;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.zusmart.base.logging.Logger;
import com.zusmart.base.logging.LoggerFactory;
import com.zusmart.base.scanner.Scanner;
import com.zusmart.base.scanner.ScannerProcessor;
import com.zusmart.base.scanner.ScannerResult;
import com.zusmart.base.scanner.ScannerResultForClass;
import com.zusmart.base.scanner.ScannerResultForResource;
import com.zusmart.base.scanner.support.DefaultScanner;
import com.zusmart.base.util.AnnotationUtils;
import com.zusmart.base.util.Assert;
import com.zusmart.base.util.ReflectionUtils;
import com.zusmart.base.util.StringUtils;
import com.zusmart.core.bean.BeanDefinition;
import com.zusmart.core.bean.BeanDefinitionRegistry;
import com.zusmart.core.bean.annotation.AnnotationBeanDefinitionRegistry;
import com.zusmart.core.bean.annotation.Bean;
import com.zusmart.core.bean.annotation.Component;
import com.zusmart.core.bean.annotation.ComponentScan;
import com.zusmart.core.bean.annotation.Configuration;
import com.zusmart.core.bean.annotation.ImportResource;
import com.zusmart.core.bean.cause.BeanDefinitionMultipleException;
import com.zusmart.core.config.ConfigSourceManager;
import com.zusmart.core.context.ApplicationContext;
import com.zusmart.core.context.support.AbstractApplicationContext;
import com.zusmart.core.plugin.Plugin;
import com.zusmart.core.plugin.PluginManager;

public class AnnotationApplicationContext extends AbstractApplicationContext {

	private static final Logger logger = LoggerFactory.getLogger(AnnotationApplicationContext.class);

	private final Class<?> runner;

	public AnnotationApplicationContext(String[] args, Class<?> runner) {
		super(args);
		Assert.isNull(runner, "runner class must not be null");
		this.runner = runner;
	}

	public AnnotationApplicationContext(Class<?> runner) {
		this(null, runner);
	}

	@Override
	protected BeanDefinitionRegistry createBeanDefinitionRegistry() {
		return new AnnotationBeanDefinitionRegistry(this);
	}

	@Override
	protected void resolveBeanDefinitionMappings() {
		Set<String> packages = this.getScanPackages();
		if (null == packages || packages.isEmpty()) {
			return;
		}
		Scanner scanner = new DefaultScanner();
		scanner.addPatterns(packages);
		scanner.addProcessor(new ScannerProcessor() {
			private boolean isConfiguration(ScannerResultForClass target) {
				Class<?> targetClass = target.getTargetClass();
				return AnnotationUtils.hasAnnotation(targetClass, Configuration.class) || classHasExtendAnnotation(targetClass, Configuration.class);
			}

			private boolean isComponent(ScannerResultForClass target) {
				Class<?> targetClass = target.getTargetClass();
				if (targetClass.isAnnotation()) {
					return false;
				}
				return AnnotationUtils.hasAnnotation(targetClass, Component.class) || classHasExtendAnnotation(targetClass, Component.class);
			}

			@Override
			public boolean process(ScannerResult result) {
				if (result.isClass()) {
					return this.isConfiguration((ScannerResultForClass) result) || this.isComponent((ScannerResultForClass) result);
				}
				return false;
			}

		});
		Set<ScannerResult> results = scanner.scan();
		if (null != results && results.size() > 0) {
			BeanDefinitionRegistry beanDefinitionRegistry = this.getBeanDefinitionRegistry();
			for (ScannerResult sr : results) {
				if (sr.isClass()) {
					ScannerResultForClass scr = (ScannerResultForClass) sr;
					try {
						BeanDefinition beanDefinition = this.beanDefinitionRegistry.attachBeanDefinition(scr.getTargetClass());
						this.resolveBeanPlugin(beanDefinition, beanDefinitionRegistry);
						this.resolveBeanSource(beanDefinition, beanDefinitionRegistry);
						this.resolveBeanAnnotation(beanDefinition, beanDefinitionRegistry);
					} catch (BeanDefinitionMultipleException e) {
						logger.warn("bean definition was registed {}", scr.getTargetClass().getName());
					}
				}
			}
		}
	}

	protected void resolveBeanPlugin(BeanDefinition beanDefinition, BeanDefinitionRegistry beanDefinitionRegistry) {
		PluginManager pluginManager = beanDefinitionRegistry.getPluginManager();
		Map<Class<? extends Plugin>, Annotation> plugins = pluginManager.resolvePlugins(beanDefinition.getBeanClass());
		if (null == plugins || plugins.isEmpty()) {
			return;
		}
		for (Entry<Class<? extends Plugin>, Annotation> entry : plugins.entrySet()) {
			try {
				Constructor<?> constructor = entry.getKey().getConstructor(new Class<?>[] { Annotation.class, PluginManager.class, BeanDefinitionRegistry.class, ApplicationContext.class });
				if (null != constructor) {
					Plugin p = (Plugin) ReflectionUtils.invokeConstructor(constructor, new Object[] { entry.getValue(), pluginManager, beanDefinitionRegistry, this });
					pluginManager.attachPlugin(p);
				}
			} catch (Exception e) {
				logger.warn("create plugin failed {}", e.getMessage(), e);
			}
		}
	}

	protected void resolveBeanAnnotation(BeanDefinition beanDefinition, BeanDefinitionRegistry beanDefinitionRegistry) throws BeanDefinitionMultipleException {
		Class<?> targetClass = beanDefinition.getBeanClass();
		Method[] methods = ReflectionUtils.getDeclaredMethods(targetClass);
		if (null == methods || methods.length == 0) {
			return;
		}
		for (Method method : methods) {
			if (!AnnotationUtils.hasAnnotation(method, Bean.class)) {
				continue;
			}
			if (method.getReturnType().equals(Void.class)) {// 无返回值的忽略
				return;
			}
			if (method.getReturnType().isPrimitive()) {// 返回基本数据类型的忽略
				return;
			}
			Bean bean = AnnotationUtils.getAnnotation(method, Bean.class);
			String beanName = bean.value();
			if (StringUtils.isBlank(beanName)) {
				beanName = method.getName();
			}
			beanDefinitionRegistry.attachBeanDefinition(beanDefinition, method, beanName);
		}
	}

	protected void resolveBeanSource(BeanDefinition beanDefinition, BeanDefinitionRegistry beanDefinitionRegistry) {
		Class<?> beanType = beanDefinition.getBeanClass();
		if (!AnnotationUtils.hasAnnotation(beanType, ImportResource.class)) {
			return;
		}
		ImportResource importResource = AnnotationUtils.getAnnotation(beanType, ImportResource.class);
		String[] locations = importResource.value();
		if (null == locations || locations.length == 0) {
			return;
		}
		Scanner scanner = new DefaultScanner();
		scanner.addPatterns(locations);
		Set<ScannerResult> results = scanner.scan();
		if (null != results && results.size() > 0) {
			ConfigSourceManager configSourceManager = beanDefinitionRegistry.getConfigSourceManager();
			for (ScannerResult sr : results) {
				if (sr.isResource()) {
					try {
						ScannerResultForResource scannerResourceResult = (ScannerResultForResource) sr;
						String extension = scannerResourceResult.getName();
						if (extension.indexOf(".") != -1) {
							extension = extension.substring(extension.lastIndexOf(".") + 1);
						}
						if (StringUtils.isBlank(extension)) {
							logger.warn("ignore config source missing file extension {}", scannerResourceResult);
							continue;
						}
						configSourceManager.attachConfigSource(scannerResourceResult.getInputStream(), scannerResourceResult.getName(), extension);
					} catch (IOException e) {
						logger.warn("attach config source failed {}", e.getMessage(), e);
					}
				}
			}
		}
	}

	protected Set<String> getScanPackages() {
		String packageName = this.runner.getPackage() == null ? "" : this.runner.getPackage().getName() + ".**";
		Set<String> result = new LinkedHashSet<String>();
		result.add(packageName);
		this.doScanPackages(packageName, result);
		return result;
	}

	protected void doScanPackages(String parent, Set<String> packages) {
		Scanner scanner = new DefaultScanner();
		scanner.addPattern(parent);
		scanner.addProcessor(new ScannerProcessor() {
			@Override
			public boolean process(ScannerResult result) {
				if (result.isClass()) {
					ScannerResultForClass target = (ScannerResultForClass) result;
					return AnnotationUtils.hasAnnotation(target.getTargetClass(), Configuration.class) || classHasExtendAnnotation(target.getTargetClass(), Configuration.class);
				}
				return false;
			}
		});
		Set<ScannerResult> results = scanner.scan();
		if (null != results && results.size() > 0) {
			for (ScannerResult sr : results) {
				if (sr.isClass()) {
					ScannerResultForClass scr = (ScannerResultForClass) sr;
					Class<?> targetClass = scr.getTargetClass();
					if (AnnotationUtils.hasAnnotation(targetClass, ComponentScan.class)) {
						String[] childPackages = AnnotationUtils.getAnnotation(targetClass, ComponentScan.class).value();
						if (null == childPackages || childPackages.length == 0) {
							continue;
						}
						for (String childPackage : childPackages) {
							if (StringUtils.isBlank(childPackage)) {
								continue;
							}
							if (packages.contains(childPackage)) {
								continue;
							}
							packages.add(childPackage);
							this.doScanPackages(childPackage, packages);
						}
					}
				}
			}
		}
	}

	protected boolean classHasExtendAnnotation(Class<?> classType, Class<? extends Annotation> annotationClass) {
		Annotation[] annotations = classType.getAnnotations();
		if (null == annotations || annotations.length == 0) {
			return false;
		}
		for (Annotation annotation : annotations) {
			if (AnnotationUtils.hasAnnotation(annotation.annotationType(), annotationClass)) {
				return true;
			}
		}
		return false;
	}

}